use super::{
    cur_time, err, err_value, json, ok, throw_err, Base, Client, ECode, MapSv, Resp, TBase, VBase,
    VMap, Value,
};
use base::tbase::log_stock;
use tracing::{instrument, warn};

pub struct Sale;

impl Base for Sale {}
impl TBase for Sale {}

impl Sale {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "back" => self.do_back(line, data, client).await,
            "fin" => self.do_fin(line, data, client).await,
            "finbuy" => self.do_finbuy(line, data, client).await,
            "listback" => self.do_listback(line, client).await,
            "in" => self.do_in(line, data, client).await,
            "order" => self.do_order(line, data, client).await,
            "out" => self.do_out(line, data, client).await,
            "toback" => self.do_toback(line, data, client).await,
            _ => throw_err!(ECode::NoServe, "no sale serve"),
        }
    }

    #[instrument(skip_all, name = "do_listback")]
    async fn do_listback(&self, line: &str, client: &Client) -> Resp {
        let cmd = json!({ "line": line });

        let body = client.cfgdb(&cmd, "file/list/back").await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str("sn")?;

            let val = match d.k_array("v") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let cmd = json!({ "line": line, "k": sn });

            let body_buy = client.cfgdb(&cmd, "buy/get").await?;
            let mut body_buy = body_buy.ck_obj("data")?.clone();

            let mut dnum = 0;

            for t in val.iter() {
                let num = t.ck_i64("num")?;
                dnum += num;
            }

            body_buy.insert("dnum".to_string(), json!(dnum));
            body_buy.insert("v".to_string(), json!(val));

            ret.push(body_buy);
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_toback")]
    async fn do_toback(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let sn = data.ck_str("k")?;
        let ngsn = data.ck_str("sn")?;
        let ngson = data.ck_str("ngson")?;
        let path = data.ck_str("path")?;
        let num = data.ck_i64("num")?;

        let cmd = json!({ "line": line, "sn": sn });

        let v = json!({
            "path": path,
            "sn": ngsn,
            "ngson": ngson,
            "num": num,
        });

        let val = match client.cfgdb(&cmd, "file/get/back").await {
            Ok(mut body) => {
                let body = body.ck_obj_mut("data")?.ck_array_mut("v")?;
                body.push(v);

                json!(body)
            }
            Err(_) => {
                json!([v])
            }
        };

        let cmd = json!({
            "line": line,
            "sn": sn,
            "v": {
                "sn": sn,
                "v": val,
            },
        });

        client.cfgdb(&cmd, "file/md/back").await?;

        let cmd = json!({
            "line": line,
            "sn": ngsn,
            "v": {
                "state": "rej",
            },
        });

        client.cfgdb(&cmd, &format!("file/md/{path}")).await
    }

    #[instrument(skip_all, name = "do_finbuy")]
    async fn do_finbuy(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let sn = data.ck_str("k")?;

        let cmd = json!({ "line": line, "k": sn });

        let body = client.cfgdb(&cmd, "buy/get").await?;
        let body = body.ck_obj("data")?;
        let kind = body.ck_str("kind")?;

        client.cfgdb(&cmd, "buy/rm").await?;

        warn!("finbuy order log");

        let k = json!({
            "tsn": "fin"
        });

        let _ = log_stock(line, "lgbuy", sn, kind, &body, Some(k), client, "").await;

        ok!()
    }

    #[instrument(skip_all, name = "do_back")]
    async fn do_back(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let sn = data.ck_str("k")?;

        let cmd = json!({ "line": line, "sn": sn });

        let body_back = client.cfgdb(&cmd, "file/get/back").await?;
        let body_back = body_back.ck_obj("data")?.ck_array("v")?;

        let mut num = 0;
        let mut log = Vec::new();

        warn!("back: rm ng order");

        for d in body_back.iter() {
            let d = d.ckobj()?;

            let path = d.ck_str("path")?;
            let ng_sn = d.ck_str("sn")?;
            let ngson = d.ck_str("ngson")?;
            let ng_num = d.ck_i64("num")?;

            num += ng_num;

            let cmd = json!({ "line": line, "sn": ng_sn });

            if let Ok(body) = client.cfgdb(&cmd, &format!("file/get/{path}")).await {
                let dnum = body.ck_obj("data")?.ck_i64("num")?;
                let left = dnum - ng_num;

                if left <= 0 {
                    let _ = client.cfgdb(&cmd, &format!("file/rm/{path}")).await;
                } else {
                    let cmd = json!({
                        "line": line,
                        "sn": ng_sn,
                        "v": {
                            "num": left,
                        }
                    });
                    let _ = client.cfgdb(&cmd, &format!("file/md/{path}")).await;
                }
            }

            log.push((ng_num, ngson.to_string()))
        }

        warn!("back: rm back order");

        let cmd = json!({ "line": line, "sn": sn });
        let _ = client.cfgdb(&cmd, "file/rm/back").await;

        warn!("back: update sale order num");

        let cmd = json!({ "line": line, "k": sn });

        let body = client.cfgdb(&cmd, "buy/get").await?;
        let body = body.ck_obj("data")?;

        let kind = body.ck_str("kind")?;
        let old_num = match body.k_i64("num") {
            Some(v) => v,
            None => 0,
        };

        let mut cur_num = old_num - num;
        if cur_num < 0 {
            cur_num = 0;
        }

        let cmd = json!({
            "line": line,
            "v": {
                "sn": sn,
                "num": cur_num
            }
        });

        client.cfgdb(&cmd, "buy/mditem").await?;

        warn!("back order log");

        for (num, ngson) in log.iter() {
            let mut v = body.clone();
            v.insert("num".to_string(), json!(num));
            v.insert("ngson".to_string(), json!(ngson));

            let k = json!({
                "tsn": "back"
            });

            let _ = log_stock(line, "lgbuy", sn, kind, &v, Some(k), client, "").await;
        }

        ok!()
    }

    #[instrument(skip_all, name = "do_in")]
    async fn do_in(&self, line: &str, para: &MapSv, client: &Client) -> Resp {
        let data = para.ck_obj("v")?;

        let sn = data.ck_str("sn")?;
        let num = data.ck_i64("num")?;
        let uname = match data.k_str("uname") {
            Some(v) => v,
            None => "",
        };

        let cmd = json!({ "line": line, "k": sn });

        let body = client.cfgdb(&cmd, "buy/get").await?;
        let body = body.ck_obj("data")?;

        let kind = body.ck_str("kind")?;
        let old_num = match body.k_i64("num") {
            Some(v) => v,
            None => 0,
        };

        let cur_num = old_num + num;

        warn!("in: update cur order");

        let cmd = json!({
            "line": line,
            "v": {
                "sn": sn,
                "num": cur_num,
                "end": cur_time("%Y-%m-%d %H:%M:%S"),
            }
        });

        client.cfgdb(&cmd, "buy/mditem").await?;

        let cmd = json!({ "line": line, "rule": kind });

        let gen = client.cfgdb(&cmd, "code/gen").await?;
        let psn = gen.ck_obj("data")?.ck_str("psn")?;

        let mut tmp = body.clone();
        tmp.insert("num".to_string(), json!(num));
        tmp.insert("order".to_string(), json!(sn));
        tmp.insert("sn".to_string(), json!(psn));
        tmp.insert("pname".to_string(), json!(uname));

        match kind {
            "part" => {
                warn!("in: part order");

                let val = json!({
                    "line": line,
                    "sn": psn,
                    "v": tmp,
                });

                client.cfgdb(&val, &format!("file/md/qa{kind}")).await?;
            }
            "oil" => {
                warn!("in: oil order");

                let tech = body.ck_str("tech")?;

                tmp.insert("custom".to_string(), json!(tech));

                let val = json!({
                    "line": line,
                    "sn": psn,
                    "v": tmp,
                });

                client.cfgdb(&val, &format!("file/md/qa{kind}")).await?;
            }
            "cage" | "seal" | "stell" | "cera" | "lathe" | "forge" => {
                warn!("in: create test order");

                if kind == "seal" {
                    let t = cur_time("%Y.%m.%d");
                    tmp.insert("NB".to_string(), json!(format!("{}/{}", t, num)));
                } else if kind == "cage" {
                    let t = cur_time("%Y.%m.%d");
                    tmp.insert("gtime".to_string(), json!(format!("{}/{}", t, num)));
                }

                let val = json!({
                    "line": line,
                    "sn": psn,
                    "v": tmp,
                });

                client.cfgdb(&val, &format!("file/md/test{kind}")).await?;
            }
            _ => {}
        }

        match kind {
            "forge" => {
                warn!("in: update outhouse num");

                let std = body.ck_str("std")?;
                let supply = body.ck_str("supply")?;

                let sn = format!("{std}-{supply}");

                let path = format!("out{kind}");
                let lgpath = format!("lg{path}");

                let cmd = json!({ "line": line, "sn": sn });

                if let Ok(body) = client.cfgdb(&cmd, &format!("file/get/{path}")).await {
                    let body = body.ck_obj("data")?;
                    let old_num = body.ck_i64("dnum")?;

                    let mut c = old_num - num;
                    if c <= 0 {
                        c = 0;
                    }

                    let cmd = json!({
                        "line": line,
                        "sn": sn,
                        "v": {
                            "dnum": c,
                        }
                    });

                    let _ = client.cfgdb(&cmd, &format!("file/md/{path}")).await;

                    let mut tmp = body.clone();
                    tmp.insert("num".to_string(), json!(num));

                    let _ = log_stock(line, &lgpath, &sn, "out", &tmp, None, client, "").await;
                }
            }
            "lathe" => {
                warn!("in: update lathe num");

                let std = body.ck_str("std")?;
                let supply = body.ck_str("supply")?;

                let sn = format!("{std}-{supply}");

                let path = format!("out{kind}");
                let lgpath = format!("lg{path}");

                let cmd = json!({ "line": line, "sn": sn });
                let mut forge_num = 0;

                if let Ok(body) = client.cfgdb(&cmd, &format!("file/get/{path}")).await {
                    let body = body.ck_obj("data")?;
                    let old_num = body.ck_i64("dnum")?;

                    if old_num > 0 {
                        warn!("in: down out lathe num");

                        let mut c = old_num - num;
                        if c <= 0 {
                            forge_num = -c;
                            c = 0;
                        }

                        let cmd = json!({
                            "line": line,
                            "sn": sn,
                            "v": {
                                "dnum": c,
                            }
                        });

                        let _ = client.cfgdb(&cmd, &format!("file/md/{path}")).await;

                        let mut tmp = body.clone();
                        tmp.insert("num".to_string(), json!(num));

                        let _ = log_stock(line, &lgpath, &sn, "out", &tmp, None, client, "").await;
                    } else {
                        warn!("in: lathe = 0");
                        forge_num = num;
                    }
                } else {
                    warn!("in: no out lathe num");
                    forge_num = num;
                }

                if forge_num > 0 {
                    warn!("in: update out forge num");

                    let forge = body.ck_str("forge")?;

                    let sn = format!("{forge}-{supply}");
                    let path = "outforge";
                    let lgpath = format!("lg{path}");

                    let cmd = json!({ "line": line, "sn": sn });

                    if let Ok(body) = client.cfgdb(&cmd, &format!("file/get/{path}")).await {
                        let body = body.ck_obj("data")?;
                        let old_num = body.ck_i64("dnum")?;

                        warn!("in: down out forge num");

                        let mut c = old_num - forge_num;
                        if c <= 0 {
                            c = 0;
                        }

                        let cmd = json!({
                            "line": line,
                            "sn": sn,
                            "v": {
                                "dnum": c,
                            }
                        });

                        let _ = client.cfgdb(&cmd, &format!("file/md/{path}")).await;

                        let mut tmp = body.clone();
                        tmp.insert("num".to_string(), json!(num));

                        let _ = log_stock(line, &lgpath, &sn, "out", &tmp, None, client, "").await;
                    }
                }
            }
            _ => {}
        }

        warn!("in: order log");

        tmp.insert("num".to_string(), json!(num));

        let _ = log_stock(
            line,
            "lgbuy",
            sn,
            kind,
            &tmp,
            Some(json!({ "tsn": "in" })),
            client,
            "",
        )
        .await;

        ok!()
    }

    #[instrument(skip_all, name = "do_order")]
    async fn do_order(&self, line: &str, para: &MapSv, client: &Client) -> Resp {
        let data = para.ck_obj("v")?;
        let mut val = data.clone();

        val.insert("state".to_string(), json!("open"));

        let kind = data.ck_str("kind")?;
        let std = match data.k_str("std") {
            Some(v) => v,
            None => "",
        };

        let cmd = json!({ "line": line, "rule": "buy" });

        let stdv = match kind {
            "lathe" => {
                let tech = data.ck_str("tech")?;
                let forge = data.ck_str("forge")?;

                let t_out = format!("{tech}-01");
                let t_in = format!("{tech}-02");

                let f_out = Some(format!("{forge}-01"));
                let f_in = Some(format!("{forge}-02"));

                match std {
                    "outer" => vec![(t_out, f_out)],
                    "inner" => vec![(t_in, f_in)],
                    "both" => vec![(t_out, f_out), (t_in, f_in)],
                    _ => throw_err!(ECode::Para, "std error"),
                }
            }
            "forge" => {
                let tech = data.ck_str("tech")?;

                let t_out = format!("{tech}-01");
                let t_in = format!("{tech}-02");

                match std {
                    "outer" => vec![(t_out, None)],
                    "inner" => vec![(t_in, None)],
                    "both" => vec![(t_out, None), (t_in, None)],
                    _ => throw_err!(ECode::Para, "std error"),
                }
            }
            "seal" => {
                let face = data.ck_str("face")?;
                if face == "both" {
                    vec![("A".to_string(), None), ("B".to_string(), None)]
                } else {
                    vec![(face.to_string(), None)]
                }
            }
            _ => vec![(std.to_string(), None)],
        };

        for (d, f) in stdv.iter() {
            let body = client.cfgdb(&cmd, "code/gen").await?;
            let sn = body.ck_obj("data")?.ck_str("psn")?;

            val.insert("sn".to_string(), json!(sn));

            if kind == "seal" {
                val.insert("face".to_string(), json!(d));
            } else {
                val.insert("std".to_string(), json!(d));
            }

            if let Some(v) = f {
                val.insert("forge".to_string(), json!(v));
            }

            let cmd = json!({ "line": line, "v": val });
            client.cfgdb(&cmd, "buy/add").await?;
        }

        ok!()
    }

    #[instrument(skip_all, name = "do_fin")]
    async fn do_fin(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let order_sn = data.ck_str("k")?;

        let cmd = json!({
            "line": line,
            "k": order_sn,
        });

        let body = client.cfgdb(&cmd, "sale/get").await?;
        let body = body.ck_obj("data")?;

        warn!("fin: chunk data");

        let tab = cur_time("%Y");

        let cmd = json!({
            "line": line,
            "tab": format!("sale{tab}"),
            "data": [{
                "psn": order_sn,
                "wsn": "order",
                "k":  {
                    "tsn": body.ck_str("tech")?,
                },
                "v": body,
            }]
        });

        let _ = client.timedb(&cmd, "chunk").await?;

        let cmd = json!({
            "line": line,
            "k": order_sn,
        });

        client.cfgdb(&cmd, "sale/rm").await
    }

    async fn do_out(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let k = data.ck_str("k")?;
        let dnum = data.ck_i64("num")?;

        let cmd = json!({ "line": line, "k": k });

        let body = client.cfgdb(&cmd, "sale/get").await?;
        let body = body.ck_obj("data")?;

        let pnum = body.ck_i64("pnum")?;
        let num = match body.k_i64("num") {
            Some(v) => v,
            None => 0,
        };

        let c_num = num + dnum;

        if c_num > pnum {
            throw_err!(ECode::Para, "num expend pnum");
        }

        let cmd = json!({
        "line": line,
        "v": {
            "sn": k,
            "num": c_num,
        }});

        client.cfgdb(&cmd, "sale/mditem").await
    }
}
